Micron Document
| SparkN0de-git | SparkN0de |


Commit 84b214cb909abdb507fd91338da5930b3f16c943


Parents : a90a451
Author : Mark Qvist <mark@unsigned.io>
Date : 2025-01-20T14:25:58+01:00

Added markdown rendering and message composing

Changes

6 files changed, 102 insertions(+), 12 deletions(-)

M sbapp/main.py +53 -1
M setup.py +2

Diff

diff --git a/sbapp/buildozer.spec b/sbapp/buildozer.spec
index 01fea20d..24721b1b 100644
--- a/sbapp/buildozer.spec
+++ b/sbapp/buildozer.spec
@@ -12,7 +12,7 @@ version.regex = __version__ = ['"](.*)['"]
version.filename = %(source.dir)s/main.py
android.numeric_version = 20250120
-requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,able_recipe,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl,typing-extensions
+requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,able_recipe,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl,typing-extensions,mistune>=3.0.2,beautifulsoup4
android.gradle_dependencies = com.android.support:support-compat:28.0.0
#android.enable_androidx = True

diff --git a/sbapp/main.py b/sbapp/main.py
index 21ff373f..6eb42940 100644
--- a/sbapp/main.py
+++ b/sbapp/main.py
@@ -19,6 +19,7 @@ import RNS
import LXMF
import time
import os
+import re
import pathlib
import base64
import threading
@@ -1523,6 +1524,50 @@ class SidebandApp(MDApp):
### Messages (conversation) screen
######################################
+
+ def md_to_bbcode(self, text):
+ if not hasattr(self, "mdconv"):
+ from md2bbcode.main import process_readme as mdconv
+ self.mdconv = mdconv
+ converted = self.mdconv(text)
+ while converted.endswith("\n"):
+ converted = converted[:-1]
+
+ return converted
+
+ def process_bb_markup(self, text):
+ st = time.time()
+ ms = int(sp(14))
+ h1s = int(sp(20))
+ h2s = int(sp(18))
+ h3s = int(sp(16))
+
+ if not hasattr(self, "pres"):
+ self.pres = []
+ res = [ [r"\[(?:code|icode).*?\]", f"[font=mono][size={ms}]"],
+ [r"\[\/(?:code|icode).*?\]", "[/size][/font]"],
+ [r"\[(?:heading)\]", f"[b][size={h1s}]"],
+ [r"\[(?:heading=1)*?\]", f"[b][size={h1s}]"],
+ [r"\[(?:heading=2)*?\]", f"[b][size={h2s}]"],
+ [r"\[(?:heading=3)*?\]", f"[b][size={h3s}]"],
+ [r"\[(?:heading=).*?\]", f"[b][size={h3s}]"], # Match all remaining lower-level headings
+ [r"\[\/(?:heading).*?\]", "[/size][/b]"],
+ [r"\[(?:list).*?\]", ""],
+ [r"\[\/(?:list).*?\]", ""],
+ [r"\n\[(?:\*).*?\]", "\n - "],
+ [r"\[(?:url).*?\]", ""], # Strip URLs for now
+ [r"\[\/(?:url).*?\]", ""],
+ [r"\[(?:img).*?\].*\[\/(?:img).*?\]", ""] # Strip images for now
+ ]
+
+ for r in res:
+ self.pres.append([re.compile(r[0], re.IGNORECASE | re.MULTILINE ), r[1]])
+
+ for pr in self.pres:
+ text = pr[0].sub(pr[1], text)
+
+ return text
+
def conversation_from_announce_action(self, context_dest):
if self.sideband.has_conversation(context_dest):
pass
@@ -2758,7 +2803,7 @@ class SidebandApp(MDApp):
str_comps = " - [b]Reticulum[/b] (MIT License)\n - [b]LXMF[/b] (MIT License)\n - [b]KivyMD[/b] (MIT License)"
str_comps += "\n - [b]Kivy[/b] (MIT License)\n - [b]Codec2[/b] (LGPL License)\n - [b]PyCodec2[/b] (BSD-3 License)"
- str_comps += "\n - [b]PyDub[/b] (MIT License)\n - [b]PyOgg[/b] (Public Domain)"
+ str_comps += "\n - [b]PyDub[/b] (MIT License)\n - [b]PyOgg[/b] (Public Domain)\n - [b]MD2bbcode[/b] (GPL3 License)"
str_comps += "\n - [b]GeoidHeight[/b] (LGPL License)\n - [b]Python[/b] (PSF License)"
str_comps += "\n\nGo to [u][ref=link]https://unsigned.io/donate[/ref][/u] to support the project.\n\nThe Sideband app is Copyright © 2025 Mark Qvist / unsigned.io\n\nPermission is granted to freely share and distribute binary copies of "+self.root.ids.app_version_info.text+", so long as no payment or compensation is charged for said distribution or sharing.\n\nIf you were charged or paid anything for this copy of Sideband, please report it to [b]license@unsigned.io[/b].\n\nTHIS IS EXPERIMENTAL SOFTWARE - SIDEBAND COMES WITH ABSOLUTELY NO WARRANTY - USE AT YOUR OWN RISK AND RESPONSIBILITY"
info = "This is "+self.root.ids.app_version_info.text+", on RNS v"+RNS.__version__+" and LXMF v"+LXMF.__version__+".\n\nHumbly build using the following open components:\n\n"+str_comps
@@ -3041,6 +3086,10 @@ class SidebandApp(MDApp):
self.sideband.config["trusted_markup_only"] = self.settings_screen.ids.settings_trusted_markup_only.active
self.sideband.save_configuration()
+ def save_compose_in_markdown(sender=None, event=None):
+ self.sideband.config["compose_in_markdown"] = self.settings_screen.ids.settings_compose_in_markdown.active
+ self.sideband.save_configuration()
+
def save_advanced_stats(sender=None, event=None):
self.sideband.config["advanced_stats"] = self.settings_screen.ids.settings_advanced_statistics.active
self.sideband.save_configuration()
@@ -3219,6 +3268,9 @@ class SidebandApp(MDApp):
self.settings_screen.ids.settings_trusted_markup_only.active = self.sideband.config["trusted_markup_only"]
self.settings_screen.ids.settings_trusted_markup_only.bind(active=save_trusted_markup_only)
+ self.settings_screen.ids.settings_compose_in_markdown.active = self.sideband.config["compose_in_markdown"]
+ self.settings_screen.ids.settings_compose_in_markdown.bind(active=save_compose_in_markdown)
+
self.settings_screen.ids.settings_ignore_invalid_stamps.active = self.sideband.config["lxmf_ignore_invalid_stamps"]
self.settings_screen.ids.settings_ignore_invalid_stamps.bind(active=save_lxmf_ignore_invalid_stamps)

diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py
index b68186b1..c4c36692 100644
--- a/sbapp/sideband/core.py
+++ b/sbapp/sideband/core.py
@@ -457,6 +457,7 @@ class SidebandCore():
self.config["eink_mode"] = True
self.config["lxm_limit_1mb"] = True
self.config["trusted_markup_only"] = False
+ self.config["compose_in_markdown"] = False
# Connectivity
self.config["connect_transport"] = False
@@ -601,6 +602,8 @@ class SidebandCore():
self.config["hq_ptt"] = False
if not "trusted_markup_only" in self.config:
self.config["trusted_markup_only"] = False
+ if not "compose_in_markdown" in self.config:
+ self.config["compose_in_markdown"] = False
if not "input_language" in self.config:
self.config["input_language"] = None
@@ -4396,7 +4399,13 @@ class SidebandCore():
fields[LXMF.FIELD_IMAGE] = image
if audio != None:
fields[LXMF.FIELD_AUDIO] = audio
- if self.has_bb_markup(content):
+ md_sig = "#!md\n"
+ if content.startswith(md_sig):
+ content = content[len(md_sig):]
+ fields[LXMF.FIELD_RENDERER] = LXMF.RENDERER_MARKDOWN
+ elif self.config["compose_in_markdown"]:
+ fields[LXMF.FIELD_RENDERER] = LXMF.RENDERER_MARKDOWN
+ elif self.has_bb_markup(content):
fields[LXMF.FIELD_RENDERER] = LXMF.RENDERER_BBCODE
lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method, fields = fields, include_ticket=self.is_trusted(destination_hash))
@@ -4538,12 +4547,12 @@ class SidebandCore():
def strip_bb_markup(self, text):
if not hasattr(self, "smr") or self.smr == None:
- self.smr = re.compile(r'\[\/?(?:b|i|u|url|quote|code|img|color|size)*?.*?\]',re.IGNORECASE | re.MULTILINE )
+ self.smr = re.compile(r"\[\/?(?:b|i|u|url|quote|code|img|color|size)*?.*?\]",re.IGNORECASE | re.MULTILINE )
return self.smr.sub("", text)
def has_bb_markup(self, text):
if not hasattr(self, "smr") or self.smr == None:
- self.smr = re.compile(r'\[\/?(?:b|i|u|url|quote|code|img|color|size)*?.*?\]',re.IGNORECASE | re.MULTILINE )
+ self.smr = re.compile(r"\[\/?(?:b|i|u|url|quote|code|img|color|size)*?.*?\]",re.IGNORECASE | re.MULTILINE )
if self.smr.match(text):
return True
else:

diff --git a/sbapp/ui/layouts.py b/sbapp/ui/layouts.py
index 4e6ab433..6155953a 100644
--- a/sbapp/ui/layouts.py
+++ b/sbapp/ui/layouts.py
@@ -1655,6 +1655,22 @@ MDScreen:
disabled: False
active: False
+ MDBoxLayout:
+ orientation: "horizontal"
+ size_hint_y: None
+ padding: [0,0,dp(24),dp(0)]
+ height: dp(48)
+
+ MDLabel:
+ text: "Compose messages in markdown"
+ font_style: "H6"
+
+ MDSwitch:
+ id: settings_compose_in_markdown
+ pos_hint: {"center_y": 0.3}
+ disabled: False
+ active: False
+
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None

diff --git a/sbapp/ui/messages.py b/sbapp/ui/messages.py
index b884b76a..87557b0c 100644
--- a/sbapp/ui/messages.py
+++ b/sbapp/ui/messages.py
@@ -110,8 +110,6 @@ class Messages():
msg = self.app.sideband.message(lxm_hash)
if msg:
close_button = MDRectangleFlatButton(text="Close", font_size=dp(18))
- # d_items = [ ]
- # d_items.append(DialogItem(IconLeftWidget(icon="postage-stamp"), text="[size="+str(ss)+"]Stamp[/size]"))
d_text = ""
@@ -492,11 +490,24 @@ class Messages():
for m in self.new_messages:
if not m["hash"] in self.added_item_hashes:
+ renderer = None
+ message_source = m["content"]
+ if "lxm" in m and m["lxm"] and m["lxm"].fields != None and LXMF.FIELD_RENDERER in m["lxm"].fields:
+ renderer = m["lxm"].fields[LXMF.FIELD_RENDERER]
+
try:
if self.app.sideband.config["trusted_markup_only"] and not self.is_trusted:
message_input = str( escape_markup(m["content"].decode("utf-8")) ).encode("utf-8")
else:
message_input = m["content"]
+ if renderer == LXMF.RENDERER_MARKDOWN:
+ message_input = self.app.md_to_bbcode(message_input.decode("utf-8")).encode("utf-8")
+ message_input = self.app.process_bb_markup(message_input.decode("utf-8")).encode("utf-8")
+ elif renderer == LXMF.RENDERER_BBCODE:
+ message_input = self.app.process_bb_markup(message_input.decode("utf-8")).encode("utf-8")
+ else:
+ message_input = str(escape_markup(m["content"].decode("utf-8"))).encode("utf-8")
+
except Exception as e:
RNS.log(f"Message content could not be decoded: {e}", RNS.LOG_DEBUG)
message_input = b""
@@ -1144,7 +1155,7 @@ class Messages():
"viewclass": "OneLineListItem",
"text": "Copy message text",
"height": dp(40),
- "on_release": gen_copy(message_input.decode("utf-8"), item)
+ "on_release": gen_copy(message_source.decode("utf-8"), item)
},
{
"text": "Delete",
@@ -1178,7 +1189,7 @@ class Messages():
"viewclass": "OneLineListItem",
"text": "Copy message text",
"height": dp(40),
- "on_release": gen_copy(message_input.decode("utf-8"), item)
+ "on_release": gen_copy(message_source.decode("utf-8"), item)
},
{
"text": "Delete",
@@ -1196,7 +1207,7 @@ class Messages():
"viewclass": "OneLineListItem",
"text": "Copy",
"height": dp(40),
- "on_release": gen_copy(message_input.decode("utf-8"), item)
+ "on_release": gen_copy(message_source.decode("utf-8"), item)
},
{
"text": "Delete",
@@ -1213,7 +1224,7 @@ class Messages():
"viewclass": "OneLineListItem",
"text": "Copy",
"height": dp(40),
- "on_release": gen_copy(message_input.decode("utf-8"), item)
+ "on_release": gen_copy(message_source.decode("utf-8"), item)
},
{
"viewclass": "OneLineListItem",
@@ -1236,7 +1247,7 @@ class Messages():
"viewclass": "OneLineListItem",
"text": "Copy",
"height": dp(40),
- "on_release": gen_copy(message_input.decode("utf-8"), item)
+ "on_release": gen_copy(message_source.decode("utf-8"), item)
},
{
"text": "Delete",

diff --git a/setup.py b/setup.py
index e6b2cf7c..6a5f879b 100644
--- a/setup.py
+++ b/setup.py
@@ -123,6 +123,8 @@ setuptools.setup(
"ffpyplayer",
"sh",
"numpy<=1.26.4",
+ "mistune>=3.0.2",
+ "beautifulsoup4",
"pycodec2;sys.platform!='Windows' and sys.platform!='win32' and sys.platform!='darwin'",
"pyaudio;sys.platform=='linux'",
"pyobjus;sys.platform=='darwin'",


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────